package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.pojo.product.vo.SpuDetailStandardVO;
import cn.tedu.mall.pojo.product.vo.SpuStandardVO;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuDetailSimpleVO;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuVO;
import cn.tedu.mall.product.service.seckill.IForSeckillSpuService;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.service.ISeckillSpuService;
import cn.tedu.mall.seckill.utils.RedisBloomUtils;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SeckillSpuServiceImpl implements ISeckillSpuService {
    //SeckillCacheUtils类中没有定义SpuDetail对应的key的常量，所以我们自己定义一个
    public static final String SECKILL_SPU_DETAIL_VO_PREFIX="seckill:spu:detail:vo:";
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SeckillSpuMapper seckillSpuMapper;
    //seckill_spu中没有spu的具体信息，具体信息需要从pms库中获取，需要product调用
    @DubboReference
    private IForSeckillSpuService dubboSeckillSpuService;

    @Autowired
    private RedisBloomUtils redisBloomUtils;

    @Override
    public JsonPage<SeckillSpuVO> listSeckillSpus(Integer page, Integer pageSize) {
        //分页查询，设置查询条件
        PageHelper.startPage(page,pageSize);
        List<SeckillSpu> seckillSpus = seckillSpuMapper.findSeckillSpus();
        //我们需要将seckillSpus集合中的SeckillSpu对象转换成SeckillSpuVO才能返回
        List<SeckillSpuVO> seckillSpuVOS = new ArrayList<>();
        //遍历seckillSpus集合进行对象类型的转化
        for (SeckillSpu seckillSpu : seckillSpus) {
           //获取spuId，根据spuId获取pms_spu表中的信息
            Long spuId = seckillSpu.getSpuId();
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //SpuStandardVO大部分数据和SeckillSpuVO中是同名属性，可以直接赋值
            SeckillSpuVO seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //将seckillSpu对象中的秒杀信息赋值到seckillSpuVO
            //赋值秒杀价格
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice());
            //赋值开始时间和结束时间
            seckillSpuVO.setStartTime(seckillSpu.getStartTime());
            seckillSpuVO.setEndTime(seckillSpu.getEndTime());
            //将包含商品信息和秒杀信息的seckillSpuVO保存到集合中
            seckillSpuVOS.add(seckillSpuVO);
        }
        //返回分页结果
        return JsonPage.restPage(new PageInfo<>(seckillSpuVOS));
    }

    //根据spuId查询spu详情
    @Override
    public SeckillSpuVO getSeckillSpu(Long spuId) {
        //先判断当前spuId是否在布隆过滤器中
        //获取布隆过滤器中对应的key
        String bloomFilterKey = SeckillCacheUtils.getBloomFilterKey(LocalDate.now());
        log.info("当前批次的布隆过滤器的key为{}",bloomFilterKey);
        //如果不存在，程序就此终止
        if (!redisBloomUtils.bfexists(bloomFilterKey,spuId+"")){
            //抛出异常
            throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在(布隆测试)");
        }
        //在布隆过滤器中存在
        //声明一个返回值类型的对象
        SeckillSpuVO seckillSpuVO = null;
        //获取seckillSpuVO对象的key值
        String seckillSpuVOKey = SeckillCacheUtils.getSeckillSpuVOKey(spuId);
        //判断Redis中是否有该key
        if(redisTemplate.hasKey(seckillSpuVOKey)){
            //有直接返回
            seckillSpuVO = (SeckillSpuVO) redisTemplate.boundValueOps(seckillSpuVOKey).get();
        }else{
            //redis中没有就从数据库中查询
            //需要查询两张表：seckill_spu、pms_spu
            SeckillSpu seckillSpu = seckillSpuMapper.findSeckillSpuById(spuId);
            //判断seckillSpu是否null，抛出异常(布隆过滤器发生误判时，会进入到这里)
            if(seckillSpu==null)
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在");
            //秒杀信息查询到了，接下来查询pms_spu表中的数据
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //将spuStandardVO对象中的同名属性赋值到seckillSpuVoO对象中
            seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //秒杀信息赋值到seckillSpuVO对象中
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice());
            seckillSpuVO.setStartTime(seckillSpu.getStartTime());
            seckillSpuVO.setEndTime(seckillSpu.getEndTime());
            //将赋值结束的seckillSpuVO对象缓存到redis中
            redisTemplate.boundValueOps(seckillSpuVOKey).set(seckillSpuVO
                    ,24*60*60+RandomUtils.nextInt(100),TimeUnit.SECONDS);
        }
        //判断当前时间是否在秒杀时间范围内
        //此处不再查询数据库
        LocalDateTime nowTime = LocalDateTime.now();
        //判断当前时间是否大于开始时间并且小于结束时间
        //使用时间对象Duration，计算时间差
        //时间差为负值就会返回negative
        //判断当前时间是否大于开始时间
        Duration afterTime = Duration.between(nowTime, seckillSpuVO.getStartTime());
        //判断结束时间是否大于当前时间
        Duration beforeTime = Duration.between(seckillSpuVO.getEndTime(), nowTime);
        //如果afterTime和beforeTime返回值都是negative，就说明当前时间大于开始时间并且小于结束时间
        if(afterTime.isNegative() && beforeTime.isNegative()){
            //在秒杀时间范围内，可以购买，需要提供购买的url
            //url属性在seckillSpuVO对象中，我们需要进行赋值
            //url的值：/seckill/{randomCode}
            //获取随机码的key
            String randCodeKey = SeckillCacheUtils.getRandCodeKey(spuId);
            int randomCode = (int) redisTemplate.boundValueOps(randCodeKey).get();
            seckillSpuVO.setUrl("/seckill/"+randomCode);

        }
        //seckillSpuVO对象中包含了Url以及随机码，这个随机码会返回给前端保存
        //前端只有利用这个url才能发起购买的请求
        return seckillSpuVO;
    }
    //根据spuId查询spu的detail信息
    @Override
    public SeckillSpuDetailSimpleVO getSeckillSpuDetail(Long spuId) {
        //获取key
        String seckillDetailKey = SECKILL_SPU_DETAIL_VO_PREFIX +spuId;
        //声明一个返回类型的对象
        SeckillSpuDetailSimpleVO seckillSpuDetailSimpleVO = null;
        //判断redis中是否已经存在该商品的信息
        if(redisTemplate.hasKey(seckillDetailKey)){
            //缓存中已经有了，直接返回
            seckillSpuDetailSimpleVO =
                    (SeckillSpuDetailSimpleVO) redisTemplate.boundValueOps(seckillDetailKey).get();
        }else{
            //缓存中没有数据，直接查询数据
            SpuDetailStandardVO spuDetailStandardVO = dubboSeckillSpuService.getSpuDetailById(spuId);
            seckillSpuDetailSimpleVO = new SeckillSpuDetailSimpleVO();
            BeanUtils.copyProperties(spuDetailStandardVO,seckillSpuDetailSimpleVO);
            //将seckillSpuDetailSimpleVO保存到redis中
            redisTemplate.boundValueOps(seckillDetailKey).set(seckillSpuDetailSimpleVO
                    ,24*60*60 + RandomUtils.nextInt(100), TimeUnit.SECONDS);
        }
        //千万要记得放回结果
        return seckillSpuDetailSimpleVO;
    }
}
